package org.pada.domain.config.doc;

import com.github.kongchen.swagger.docgen.GenerateException;
import com.github.kongchen.swagger.docgen.reader.ClassSwaggerReader;
import com.github.kongchen.swagger.docgen.reader.SpringExceptionHandlerReader;
import com.github.kongchen.swagger.docgen.reader.SpringMvcApiReader;
import com.github.kongchen.swagger.docgen.spring.SpringResource;
import io.swagger.annotations.ApiResponses;
import io.swagger.converter.ModelConverters;
import io.swagger.models.*;
import io.swagger.models.parameters.Parameter;
import io.swagger.models.properties.Property;
import io.swagger.models.properties.RefProperty;
import org.apache.maven.plugin.logging.Log;
import org.pada.domain.cmd.CmdHub;
import org.pada.domain.cmd.ICmdHandler;
import org.pada.domain.util.JsonUtil;
import org.reflections.Reflections;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation;
import static org.springframework.core.annotation.AnnotationUtils.findAnnotation;

/**
 * @author shen.peng
 * @since 2025/5/7
 */
public class SwaggerApiReader extends SpringMvcApiReader implements ClassSwaggerReader {

    private static final ResponseContainerConverter RESPONSE_CONTAINER_CONVERTER = new ResponseContainerConverter();
    private static List<Annotation> annotations =
            Arrays.asList(CmdHub.Route.class.getDeclaredMethods()[0].getParameterAnnotations()[0]);
    private final SpringExceptionHandlerReader exceptionHandlerReader;

    public SwaggerApiReader(Swagger swagger, Log log) {
        super(swagger, log);
        exceptionHandlerReader = new SpringExceptionHandlerReader(log);
    }

    @Override
    public Swagger read(Set<Class<?>> set) throws GenerateException {
        super.read(set);
        Reflections reflections = new Reflections("");
        Set<Class<? extends ICmdHandler>> cmd = reflections.getSubTypesOf(ICmdHandler.class);
        Map<String, SpringResource> resourceMap = generateCmdResourceMap(cmd);
        //LOG.info("map :" + JsonUtil.toJSONString(resourceMap.keySet()));
        resourceMap.values()
                .forEach(this::read);
        LOG.info("swagger: " + JsonUtil.toJSONString(swagger));
        return swagger;
    }

    public Swagger read(SpringResource resource) {
        if (swagger == null) {
            swagger = new Swagger();
        }
        //LOG.info("resource :" + resource.getResourcePath());
        Map<String, Tag> tags = new HashMap<String, Tag>();

        List<SecurityRequirement> resourceSecurities = new ArrayList<SecurityRequirement>();


        Map<String, String> regexMap = new HashMap<String, String>();
        String operationPath = parseOperationPath(resource.getResourcePath(), regexMap);

        String httpMethod = RequestMethod.POST.toString()
                .toLowerCase();
        Operation operation = parseMethod(resource.getMethods()
                .get(0), RequestMethod.POST);


        updateOperationParameters(new ArrayList<Parameter>(), regexMap, operation);


        String[] apiProduces = new String[0];
        String[] apiConsumes = new String[0];

        apiConsumes = updateOperationConsumes(new String[0], apiConsumes, operation);
        apiProduces = updateOperationProduces(new String[0], apiProduces, operation);

        updateOperation(apiConsumes, apiProduces, tags, resourceSecurities, operation);
        LOG.info("operation: " + JsonUtil.toJSONString(operation));
        updatePath(operationPath, httpMethod, operation);

        return swagger;
    }

    private Operation parseMethod(Method method, RequestMethod requestMethod) {
        int responseCode = 200;
        Operation operation = new Operation();

        String responseContainer = null;
        Class<?> cmd = method.getParameterTypes()[0];
        String operationId = cmd.getSimpleName();

        Type responseClass = method.getGenericReturnType();

        if (responseClass instanceof ParameterizedType && ResponseEntity.class.equals(((ParameterizedType) responseClass).getRawType())) {
            responseClass = ((ParameterizedType) responseClass).getActualTypeArguments()[0];
        }

        if (responseClass != null
                && !responseClass.equals(Void.class)
                && !responseClass.equals(ResponseEntity.class)) {
            if (isPrimitive(responseClass)) {
                Property property = ModelConverters.getInstance()
                        .readAsProperty(responseClass);
                if (property != null) {
                    Property responseProperty = RESPONSE_CONTAINER_CONVERTER.withResponseContainer(responseContainer,
                            property);
                    operation.response(responseCode, new Response()
                            .description("successful operation")
                            .schema(responseProperty));
                }
            } else if (!responseClass.equals(Void.class) && !responseClass.equals(void.class)) {
                Map<String, Model> models = ModelConverters.getInstance()
                        .read(responseClass);
                if (models.isEmpty()) {
                    Property pp = ModelConverters.getInstance()
                            .readAsProperty(responseClass);
                    operation.response(responseCode, new Response()
                            .description("successful operation")
                            .schema(pp));
                }
                for (String key : models.keySet()) {
                    Property responseProperty = RESPONSE_CONTAINER_CONVERTER.withResponseContainer(responseContainer,
                            new RefProperty().asDefault(key));
                    operation.response(responseCode, new Response()
                            .description("successful operation")
                            .schema(responseProperty));
                    swagger.model(key, models.get(key));
                }
            }
            Map<String, Model> models = ModelConverters.getInstance()
                    .readAll(responseClass);
            for (Map.Entry<String, Model> entry : models.entrySet()) {
                swagger.model(entry.getKey(), entry.getValue());
            }
        }

        operation.operationId(operationId);


        ApiResponses responseAnnotation = findMergedAnnotation(method, ApiResponses.class);
        if (responseAnnotation != null) {
            updateApiResponse(operation, responseAnnotation);
        } else {
            ResponseStatus responseStatus = findMergedAnnotation(method, ResponseStatus.class);
            if (responseStatus != null) {
                operation.response(responseStatus.value()
                        .value(), new Response().description(responseStatus.reason()));
            }
        }

//        List<ResponseStatus> errorResponses = exceptionHandlerReader.getResponseStatusesFromExceptions(method);
//        for (ResponseStatus responseStatus: errorResponses) {
//            int code = responseStatus.code().value();
//            String description = defaultIfEmpty(responseStatus.reason(), responseStatus.code().getReasonPhrase());
//            operation.response(code, new Response().description(description));
//        }


        Deprecated annotation = findAnnotation(method, Deprecated.class);
        if (annotation != null) {
            operation.deprecated(true);
        }

        // process parameters
        Class[] parameterTypes = method.getParameterTypes();
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
        for (int i = 0; i < parameterTypes.length; i++) {
            Type type = genericParameterTypes[i];
            LOG.info("id: " + operationId + " type: " + type.getTypeName());
            List<Parameter> parameters = getParameters(type, annotations);
            LOG.info("param: " + JsonUtil.toJSONString(parameters));
            for (Parameter parameter : parameters) {
                if (parameter.getName()
                        .isEmpty()) {
                    parameter.setName(parameterNames[i]);
                }
                operation.parameter(parameter);
            }
        }

        if (operation.getResponses() == null) {
            operation.defaultResponse(new Response().description("successful operation"));
        }

        this.readImplicitParameters(method, operation);

        return operation;
    }

    private Map<String, SpringResource> generateCmdResourceMap(Set<Class<? extends ICmdHandler>> classes) {
        Map<String, SpringResource> resourceMap = new HashMap<String, SpringResource>();
        for (Class<? extends ICmdHandler> aClass : classes) {
            try {
                for (Method method : aClass.getDeclaredMethods()) {
                    if (!"exec".equals(method.getName()) || method.isBridge()) {
                        continue;
                    }
                    String resourceKey = aClass.getCanonicalName();
                    String cmdName = method.getParameterTypes()[0].getName();
                    //LOG.info("method: " + method.toString());
                    //LOG.info("class :" + aClass.getSimpleName() + " cmdName :" + cmdName);
                    if (!resourceMap.containsKey(resourceKey)) {
                        resourceMap.put(
                                resourceKey,
                                new SpringResource(aClass, cmdName, resourceKey, ""));
                    }
                    resourceMap.get(resourceKey)
                            .addMethod(method);
                }
            } catch (NoClassDefFoundError e) {
                LOG.error(e.getMessage());
                LOG.info(aClass.getName());
            }
        }
        return resourceMap;
    }
}
